/*
 * controller - decision maker of ccplayer
 *
 * $Id: main.c 31 2009-07-13 09:37:43Z ybc2084 $
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <mad.h>

#include "param.h"
#include "queue.h"
#include "msg.h"
#include "sock.h"

/************************** declaration *******************************/

/*
 * for indexing port descriptor in socket array
 */
enum {
    /* listening port */
    DECCOMMPORT_INDEX = 0,
    DECDATAPORT_INDEX,
    PLYCOMMPORT_INDEX,
    PLYDATAPORT_INDEX,
    GUICOMMPORT_INDEX,
    GUIDATAPORT_INDEX,

    /* connected port */
    /*CONNECTED_DECCOMMPORT,
    CONNECTED_DECDATAPORT,
    CONNECTED_PLYCOMMPORT,
    CONNECTED_PLYDATAPORT,
    CONNECTED_GUICOMMPORT,
    CONNECTED_GUIDATAPORT,*/
}; 

/*
 * port base and port number
 */
enum {
    LISTENING_PORT_NUMBER = GUIDATAPORT_INDEX - DECCOMMPORT_INDEX + 1,
    CONNECTED_PORT_NUMBER = LISTENING_PORT_NUMBER,
    COMMON_PORT_NUMBER    = LISTENING_PORT_NUMBER,

    TOTAL_PORT_NUMBER     = LISTENING_PORT_NUMBER + CONNECTED_PORT_NUMBER,
};

/*
 * socekt type
 */
typedef enum {
    LISTENING_SOCKET,
    CONNECTED_SOCKET,
    NOTINITED_SOCKET,
}SockType;

/*
 * listening socket bindings
 */
typedef struct ListeningSockBinding {
    int         sock;       /* listening socket descriptor */
    SockType    type;       /* socket type: LISTENING_SOCKET */
    int         accepted;   /* 1 - accepted, 0 - not accepted */
}ListeningSockBinding;

/*
 * connected socket and its incoming/outgoing queue
 */
typedef struct ConnectedSockBinding {
    int         sock;       /* connected socket descriptor */
    SockType    type;       /* socket type: CONNECTED_SOCKET */
    int         connected;  /* 1 - connected, 0 - not yet */
    Queue      *iqueue;     /* input queue */
    Queue      *oqueue;     /* output queue */
}ConnectedSockBinding;

/*
 * socket binding, both listening and connected
 */
typedef struct SockBinding {
    ListeningSockBinding listeningSockBinding;
    ConnectedSockBinding connectedSockBinding;
}SockBinding;

/*
 * runtime configuration for controller
 */
static struct CntlConf {
    char * audfile;     /* audio file or path */
} Config;


/********** prototypes ************/
static int handleNewSocket(SockBinding *sockq);
static int handleReadCmdOrData(SockBinding *sockq);
static int handleWriteCmdOrData(SockBinding *sockq);

/************************** implementation ****************************/

/*
 * socket-binding collection
 */
static SockBinding CntlSocks[COMMON_PORT_NUMBER];

static int parseCmdOptions(int argc, char *argv[])
{
    if (argc < 2)
        return (-1);

    Config.audfile = strdup(argv[2]);

    return (0);
}

/*
 * init all sockets
 */
static int initSockets(void)
{
    int i;
    for (i = 0; i < COMMON_PORT_NUMBER; i++) {
        /* init listening socket-binding */
        CntlSocks[i].listeningSockBinding.sock      = -1;
        CntlSocks[i].listeningSockBinding.type      = NOTINITED_SOCKET;
        CntlSocks[i].listeningSockBinding.accepted  = 0;

        /* init connected socket-binding */
        CntlSocks[i].connectedSockBinding.sock      = -1;
        CntlSocks[i].connectedSockBinding.type      = NOTINITED_SOCKET;
        CntlSocks[i].connectedSockBinding.connected = 0;
        CntlSocks[i].connectedSockBinding.iqueue    = NULL;
        CntlSocks[i].connectedSockBinding.oqueue    = NULL;
    }

    return (0);
}

/*
 * start listening on listening sockets
 */
static int openListenPorts(void)
{
    int i;
    int sock;

    initSockets();

    for (i = 0; i < LISTENING_PORT_NUMBER; i++) {
        sock = listenOn(LOOPBACKIP, CCP_PORT_BASE + i);
        assert(sock);

        CntlSocks[i].listeningSockBinding.sock     = sock;
        CntlSocks[i].listeningSockBinding.type     = LISTENING_SOCKET;
        CntlSocks[i].listeningSockBinding.accepted = 0;
    }

    return (0);
}

/*
 * some socket is ready, handle it (or them).
 *  prfds -     pointer to read fd set
 *  pwfds -     pointer to write fd set
 *
 * return 0 if success, otherwise -1.
 */
static int someoneReady(fd_set *prfds, fd_set *pwfds)
{
    int      i;
    int      sock;
    SockType type;  /* socket type */

    /* check listening sockets */
    for (i = 0; i < COMMON_PORT_NUMBER; i++) {

        sock = CntlSocks[i].listeningSockBinding.sock;
        type = CntlSocks[i].listeningSockBinding.type;

        /* ready for reading */
        if (FD_ISSET(sock, prfds) && type == LISTENING_SOCKET) {

            handleNewSocket(&CntlSocks[i]);

        } else {
            /* weird behavior, FIXME: log? */
            fprintf(stderr, "pselect() report that a "
                    "not-initialized socket is ready for reading "
                    "something, it's absolutely wrong!!!\n");
        }
    }

    /* check connected sockets */
    for (i = 0; i < COMMON_PORT_NUMBER; i++) {

        sock = CntlSocks[i].connectedSockBinding.sock;
        type = CntlSocks[i].connectedSockBinding.type;

        /* ready for reading/writing */
        if (FD_ISSET(sock, prfds) && (type == CONNECTED_SOCKET))
            handleReadCmdOrData(&CntlSocks[i]);

        if (FD_ISSET(sock, pwfds) && (type == CONNECTED_SOCKET))
            handleWriteCmdOrData(&CntlSocks[i]);

        /* FIXME:other situation, error */
#if 0
        if (type == LISTENING_SOCKET) { 
            /* FIXME: log? */
            fprintf(stderr, "pselect() report that a listening socket "
                    "is ready for writing, absolutely wrong!!!\n");
        }

        if (type == NOTINITED_SOCKET) {
            /* weird behavior, FIXME: log? */
            fprintf(stderr, "pselect() report that a "
                    "not-initialized socket is ready for writing "
                    "something, it's absolutely wrong!!!\n");

        }
#endif
    }

    return (0);
}

/*
 * poll on all sockets once, if new command or data ready, handle it
 */
static int pollAllSockets(void)
{
    int					maxfd;
    fd_set				rfds;
    fd_set				wfds;
    fd_set				efds;
    struct timespec		timeout;
    sigset_t		   *sigmask = NULL;
    int                 i;
    int                 sock;
    SockType            type;   /* socket type: listening, connected, 
                                   or notinited */
    int                 rc;

    /* poll network incoming and try outgoing, if outgoing data ready.
     * return directly if network inactive after timeout expired.
     */
    FD_ZERO(&rfds);
    FD_ZERO(&wfds);
    FD_ZERO(&efds);

    maxfd = -1;

    /* add listening socket */
    for (i = 0; i < COMMON_PORT_NUMBER; i++) {

        sock = CntlSocks[i].listeningSockBinding.sock;
        type = CntlSocks[i].listeningSockBinding.type;

        if (sock < 0 || type == NOTINITED_SOCKET)
            continue;

        if (sock > 0 && type == LISTENING_SOCKET) {
            FD_SET(sock, &rfds);
            maxfd = ((sock > maxfd) ? sock : maxfd);
        }

    }

    /* add connected socket */
    for (i = 0; i < COMMON_PORT_NUMBER; i++) {

        sock = CntlSocks[i].connectedSockBinding.sock;
        type = CntlSocks[i].connectedSockBinding.type;

        if (sock < 0 || type == NOTINITED_SOCKET)
            continue;

        if (sock > 0 && type == CONNECTED_SOCKET) {
            FD_SET(sock, &rfds);
            FD_SET(sock, &wfds);
            maxfd = ((sock > maxfd) ? sock : maxfd);
        }
    }

    timeout.tv_sec	= 0;	/* busy poll */
    timeout.tv_nsec	= 0;

    rc = pselect(maxfd, &rfds, &wfds, &efds, &timeout, sigmask);
    if (rc) {

        /* something is ready */
        return someoneReady(&rfds, &wfds);

    } else if (0 == rc) {

        /* just timeout, nothing ready */

    } else if (-1 == rc) {

        /* error, FIXME: should log? */

        return (-1);
    } else {
        /* shouldn't reach here, but who knows, just in case */
        /* FIXME: maybe logging here is a good idea */
    }

    return (0);
}

/*
 * handle new connected socket coming on listening socket.
 *  sockq - binding of listening socket and its in/out queue
 * return 0 indicating success, otherwise -1 if failed.
 * NOTE: sockq's inqueue and outqueue is actually not used.
 */
static int handleNewSocket(SockBinding *sockb)
{
    int connsock;

    do {
        connsock = accept(sockb->listeningSockBinding.sock, NULL, NULL);
    } while (connsock < 0 && errno == EINTR);

    if (connsock < 0) {
        /* not interrupted by system call, then it must be 
         * an error occured, FIXME: log!
         */

        fprintf(stderr, "an unexpected error on accept():%s\n",
                strerror(errno));

        return (-1);
    }

    sockb->connectedSockBinding.sock      = connsock;
    sockb->connectedSockBinding.type      = CONNECTED_SOCKET;
    sockb->connectedSockBinding.connected = 1;
    sockb->connectedSockBinding.iqueue    = NewQueue();
    sockb->connectedSockBinding.oqueue    = NewQueue();

    return (0);
}

/*
 * read command or data from socket
 */
static int handleReadCmdOrData(SockBinding *sockb)
{
    return (0);
}

/*
 * write command or data from socket
 */
static int handleWriteCmdOrData(SockBinding *sockb)
{
    return (0);
}

/*
 * main body, a big loop
 */
static int mainloop(void)
{
    for (;;) {

        /* poll on socket array */
        pollAllSockets();

    }
}

#ifndef	_CCPLAYER_TEST_		/* if defined: for testing */

int main(int argc, char *argv[])
{

    /* deal with cmdline options */
    parseCmdOptions(argc, argv);

    /* init socket array */
    initSockets();

    /* open listening socket */
    openListenPorts();

    /* enter main loop */
    mainloop();

    return (0);
}

#endif

